using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;

using Atomic.Libraries;
using Atomic.Structures;

namespace Atomic.Thermodynamics
{
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class ChemicalPotential
	{
		[NonSerialized] private Dictionary<Atom, double> values;

		public ChemicalPotential(IEnumerable<ChemicalPotentialComponent> components)
		{
			if (components == null)
			{
				throw new ArgumentNullException();
			}

			Components = new ReadOnlyList<ChemicalPotentialComponent>(components);
			Atoms = new ReadOnlyList<Atom>(components.Select(c => c.Atom).Distinct());

			if (Components.Count != Atoms.Count)
			{
				throw new ArgumentException("Each atom in the chemical potential must only be specified once.");
			}
		}
		
		public ChemicalPotential(params ChemicalPotentialComponent[] components)
			: this((IEnumerable<ChemicalPotentialComponent>)components)
		{
		}

		public ChemicalPotential(Atom atom1, double value1)
			: this(new ChemicalPotentialComponent[] { new ChemicalPotentialComponent(atom1, value1) })
		{
		}
			
		public ChemicalPotential(Atom atom1, double value1, Atom atom2, double value2)
			: this(new ChemicalPotentialComponent[] { new ChemicalPotentialComponent(atom1, value1), new ChemicalPotentialComponent(atom2, value2) })
		{
		}

		public ChemicalPotential(Atom atom1, double value1, Atom atom2, double value2, Atom atom3, double value3)
			: this(new ChemicalPotentialComponent[] { new ChemicalPotentialComponent(atom1, value1), new ChemicalPotentialComponent(atom2, value2), new ChemicalPotentialComponent(atom3, value3) })
		{
		}

		public double Value(Atom atom)
		{
			if (values == null)
			{
				values = new Dictionary<Atom, double>();
				foreach (ChemicalPotentialComponent c in Components)
				{
					values.Add(c.Atom, c.Value);
				}
			}

			double value;
			if (!values.TryGetValue(atom, out value))
			{
				throw new IndexOutOfRangeException("Chemical potential not specified for this atom.");
			}

			return value;
		}

		public ReadOnlyList<ChemicalPotentialComponent> Components
		{
			get;
			private set;
		}

		public ReadOnlyList<Atom> Atoms
		{
			get;
			private set;
		}
		
		private string DebuggerDisplay
		{
			get
			{
				return string.Join(", ", Components.Select(c => string.Format(CultureInfo.InvariantCulture, "{0} = {1:f4}", c.Atom.Symbol, c.Value)));
			}
		}
	}
}
